/*
 * Copyright (c) 2019. Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 * Morbi non lorem porttitor neque feugiat blandit. Ut vitae ipsum eget quam lacinia accumsan.
 * Etiam sed turpis ac ipsum condimentum fringilla. Maecenas magna.
 * Proin dapibus sapien vel ante. Aliquam erat volutpat. Pellentesque sagittis ligula eget metus.
 * Vestibulum commodo. Ut rhoncus gravida arcu.
 */

package cn.dyw.player;
/*
 * 11/19/04		1.0 moved to LGPL.
 * 29/01/00		Initial version. mdm@techie.com
 *-----------------------------------------------------------------------
 *   This program is free software; you can redistribute it and/or modify
 *   it under the terms of the GNU Library General Public License as published
 *   by the Free Software Foundation; either version 2 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU Library General Public License for more details.
 *
 *   You should have received a copy of the GNU Library General Public
 *   License along with this program; if not, write to the Free Software
 *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *----------------------------------------------------------------------
 */

import javazoom.jl.decoder.*;
import javazoom.jl.player.AudioDevice;
import javazoom.jl.player.FactoryRegistry;

import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;

/**
 * The <code>Player</code> class implements a simple player for playback
 * of an MPEG audio stream.
 *
 * @author Mat McGowan
 * @since 0.0.8
 */

// REVIEW: the audio device should not be opened until the
// first MPEG audio frame has been decoded. 
public class Player {
    /**
     * The current frame number.
     */
    private int frame = 0;

    /**
     * The MPEG audio bitstream.
     */
    // javac blank final bug.
    /*final*/ private Bitstream bitstream;

    /**
     * The MPEG audio decoder.
     */
    /*final*/ private Decoder decoder;

    /**
     * The AudioDevice the audio samples are written to.
     */
    private AudioDevice audio;

    /**
     * Has the player been closed?
     */
    private boolean closed = false;

    /**
     * Has the player played back all frames from the stream?
     */
    private boolean complete = false;

    private int lastPosition = 0;

    /**
     * 播放器状态
     */
    private volatile int suspend = 0;

    private PlaybackListener listener;

    private AudioFileInfo info;

    /**
     * Creates a new <code>Player</code> instance.
     */
    public Player() throws JavaLayerException {
        this(null);
    }

    /**
     * Creates a new <code>Player</code> instance.
     */
    public Player(AudioDevice device) throws JavaLayerException {
        init(device);
    }

    private void init(AudioDevice device) throws JavaLayerException {
        decoder = new Decoder();

        if (device != null) {
            audio = device;
        } else {
            FactoryRegistry r = FactoryRegistry.systemRegistry();
            audio = r.createAudioDevice();
        }
        audio.open(decoder);
    }

    /**
     * 加载资源准备播放
     *
     * @param file
     * @throws JavaLayerException
     */
    public void readyPlay(File file) throws Exception {
        // 如果不是暂停状态，就先暂停 再停止解析
        if (!isSuspend()) {
            suspend();
        }
        if (bitstream != null) {
            try {
                bitstream.close();
            } catch (Exception ex) {
                ex.printStackTrace();
            }
        }

        bitstream = new Bitstream(new FileInputStream(file));
        info = MetadataInfo.metadataBuild(file);
        listener.playbackStarted(info);
        suspend();
        if (audio != null) audio.close();
        init(null);
    }


    /**
     * Plays a number of MPEG audio frames.
     *
     * @param frames The number of frames to play.
     * @return true if the last frame was played, or false if there are
     * more frames.
     */
    public boolean play(int frames) throws JavaLayerException {
        boolean ret = true;

        while (true) {
            ret = decodeFrame();
            if (!ret) suspend();
        }

//        if (!ret) {
//            // last frame, ensure all data flushed to the audio device.
//            AudioDevice out = audio;
//            if (out != null) {
//                out.flush();
//                synchronized (this) {
//                    complete = (!closed);
//                    close();
//                }
//            }
//        }
    }

    /**
     * Cloases this player. Any audio currently playing is stopped
     * immediately.
     */
    public synchronized void close() {
        AudioDevice out = audio;
        if (out != null) {
            closed = true;
            audio = null;
            // this may fail, so ensure object state is set up before
            // calling this method.
            out.close();
            lastPosition = out.getPosition();
            try {
                bitstream.close();
            } catch (BitstreamException ex) {
            }
        }
    }

    /**
     * Returns the completed status of this player.
     *
     * @return true if all available MPEG audio frames have been
     * decoded, or false otherwise.
     */
    public synchronized boolean isComplete() {
        return complete;
    }

    /**
     * Retrieves the position in milliseconds of the current audio
     * sample being played. This method delegates to the <code>
     * AudioDevice</code> that is used by this player to sound
     * the decoded audio samples.
     */
    public int getPosition() {
        int position = lastPosition;

        AudioDevice out = audio;
        if (out != null) {
            position = out.getPosition();
        }
        return position;
    }

    /**
     * Decodes a single frame.
     *
     * @return true if there are no more frames to decode, false otherwise.
     */
    protected boolean decodeFrame() throws JavaLayerException {
        try {
            if (isSuspend()) {
                synchronized (this) {
                    if (isSuspend()) {
                        try {
                            this.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
            AudioDevice out = audio;
            if (out == null)
                return false;

            Header h = bitstream.readFrame();

            if (h == null)
                return false;

            // sample buffer set when decoder constructed
            SampleBuffer output = (SampleBuffer) decoder.decodeFrame(h, bitstream);

            synchronized (this) {
                out = audio;
                if (out != null) {
                    out.write(output.getBuffer(), 0, output.getBufferLength());
                }
            }

            bitstream.closeFrame();
            if (listener != null) {
                listener.playbackProgress(getPosition() / (info.getTrackLength() * 10));
            }
        } catch (RuntimeException ex) {
            throw new JavaLayerException("Exception decoding audio frame", ex);
        }
/*
		catch (IOException ex)
		{
			System.out.println("exception decoding audio frame: "+ex);
			return false;	
		}
		catch (BitstreamException bitex)
		{
			System.out.println("exception decoding audio frame: "+bitex);
			return false;	
		}
		catch (DecoderException decex)
		{
			System.out.println("exception decoding audio frame: "+decex);
			return false;				
		}
*/
        return true;
    }

    /**
     * 暂停
     */
    public void suspend() {
        this.suspend = 1;
    }

    public boolean isSuspend() {
        return suspend == 1;
    }

    /**
     * 继续播放
     */
    public void play() {
        if (suspend != 0) {
            synchronized (this) {
                if (suspend != 0) {
                    suspend = 0;
                    this.notify();
                }
            }
        }
    }

    public PlaybackListener getListener() {
        return listener;
    }

    public boolean isReady() {
        return bitstream != null;
    }

    public void setListener(PlaybackListener listener) {
        this.listener = listener;
    }

}
